using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using Speed;
using System.IO;
using SpeedSearch.FullText;
using System.Threading;
using System.Diagnostics;
using Speed.IO;

namespace SpeedSearch.Forms
{

    public partial class FormReindex : Form
    {

        bool isCheking = false;
        public bool AutoReindex = false;
        IndexProgress progress = new IndexProgress();
        Dictionary<string, string> notIndexedFiles;

        public FormReindex()
        {
            InitializeComponent();
        }

        private void FormReindex_Load(object sender, EventArgs e)
        {
            notIndexedFiles = new Dictionary<string, string>();
            notIndexedFiles.Add(".exe", null);
            notIndexedFiles.Add(".com", null);
            notIndexedFiles.Add(".dll", null);
            notIndexedFiles.Add(".tmp", null);

            TreeNode nodeParent = new TreeNode { Text = "Índices", Checked = true };

            var indexes = Program.Db.Indexes.OrderBy(p => p.IndexName).ToList();
            foreach (var rowi in indexes)
            {
                var nodei = new TreeNode { Text = rowi.IndexName, Tag = rowi, Checked = true };
                var dirs = rowi.GetDirectoriesRows().OrderBy(p => p.DirName).ToList();

                foreach (var rowd in dirs)
                {
                    var noded = new TreeNode { Text = rowd.DirName, ToolTipText = rowd.Path, Tag = rowd, Checked = true };
                    nodei.Nodes.Add(noded);
                }

                if (nodei.Nodes.Count > 0)
                    nodeParent.Nodes.Add(nodei);
            }

            trv.Nodes.Add(nodeParent);
            nodeParent.Expand();

            lblDirIndex.Text = "Diretório dos índices: " + Program.Db.Parameters.IndexDirectory;

            trv.AfterCheck += new TreeViewEventHandler(trv_AfterCheck);

            if (AutoReindex)
            {
                this.Show();
                Reindex();
            }
        }

        void trv_AfterCheck(object sender, TreeViewEventArgs e)
        {
            if (isCheking)
                return;

            isCheking = true;

            CheckChildren(e.Node);

            isCheking = false;
        }

        void CheckChildren(TreeNode node)
        {
            foreach (TreeNode n in node.Nodes)
            {
                n.Checked = node.Checked;
                CheckChildren(n);
            }
        }

        private void btnReindex_Click(object sender, EventArgs e)
        {
            if (!progress.IsRunning)
            {
                if (Program.Confirm(this, "Confirma a reindexação?\r\n\r\nEste processo pode ser um pouco demorado, dependendo da quantidade de arquivos processados."))
                    Reindex();
            }
            else
            {
                AppendText("Cancelando ...");
                progress.IsRunning = false;
            }
        }

        private void Reindex()
        {
            //new Thread(new ThreadStart(reindex)).Start();
            reindex();
        }

        private void reindex()
        {
            DateTime start = DateTime.Now;
            try
            {
                this.Cursor = Cursors.WaitCursor;
                progress = new IndexProgress();
                progress.IsRunning = true;

                btnReindex.Text = "Cancelar";
                trv.Enabled = false;
                txtResult.Visible = true;
                txtResult.Clear();

                AppendText("Reindexação iniciada.");

                foreach (TreeNode nodei in trv.Nodes[0].Nodes)
                {
                    Application.DoEvents();
                    try
                    {
                        var rowi = (Config.IndexesRow)nodei.Tag;
                        List<Config.DirectoriesRow> rowsd = new List<Config.DirectoriesRow>();
                        foreach (TreeNode noded in nodei.Nodes)
                        {
                            if (noded.Checked)
                                rowsd.Add((Config.DirectoriesRow)noded.Tag);
                        }

                        if (rowsd.Count > 0)
                        {
                            Reindex(rowi, rowsd);
                        }
                        if (!progress.IsRunning)
                            goto Fim;
                    }
                    catch (Exception ex)
                    {
                        progress.Exception = ex;
                        ShowProgress();
                    }
                }
            }
            catch (Exception ex)
            {
                AppendText("Erro: " + ex.Message); ;
                Program.ShowError(this, ex);
            }
        Fim:
            trv.Enabled = true;
            progress.IsRunning = false;
            btnReindex.Text = "Reindexar";
            this.Cursor = Cursors.Default;
            AppendText("Fim da indexação");
            AppendText("Tempo total: " + DateTime.Now.Subtract(start).ToString());
            Program.ShowInformation(this, "Fim da indexação");
        }

        void AppendText(string text, int tabCount = 0)
        {
            txtResult.AppendText(DateTime.Now.ToLongTimeString() + (tabCount == 0 ? "" : new String(' ', 4 * tabCount)) + " " + text.Trim() + "\r\n");
            txtResult.SelectionStart = txtResult.Text.Length - 1;
            txtResult.ScrollToCaret();
        }

        DateTime lastTime;
        TimeSpan diff = TimeSpan.FromSeconds(1);
        void ShowProgress()
        {
            if (progress.LastMessageIndex != progress.MesageIndex)
            {
                progress.LastMessageIndex = progress.MesageIndex;
                AppendText(progress.MesageIndex, 2);
            }
            //if (lblMesageDirectory.Text != progress.MesageDirectory)
            //{
            //    lblMesageDirectory.Text = progress.MesageDirectory;
            //    lblMesageDirectory.Update();
            //}
            if (DateTime.Now.Subtract(lastTime) >= diff | progress.FileCurrent % 10 == 0 | progress.FileCurrent == progress.FileCount)
            {
                lblMessageCurrent.Text = progress.MesageCurrent;
                lblMessageCurrent.Update();

                lblTotal.Text = string.Format("Total: Arquivos {0} - Novos {1} - Alterados {2} - Não alterados {3} - Removidos {4}",
                    progress.TotalFileCount, progress.TotalFileAdd, progress.TotalFileChanged, progress.TotalFileUnchanged, progress.TotalDeleteCount);
                lblTotal.Update();
            }

            if (progress.Exception != null)
            {
                AppendText("Erro: " + progress.Exception);
                progress.Exception = null;
            }
            lastTime = DateTime.Now;
            Application.DoEvents();
        }

        public void Reindex(Config.IndexesRow rowi, List<Config.DirectoriesRow> rowsd = null)
        {
            try
            {
                string indexPath = Program.GetIndexFile(rowi.IndexName);
                if (!Directory.Exists(indexPath))
                    Directory.CreateDirectory(indexPath);

                if (rowsd == null)
                    rowsd = rowi.GetDirectoriesRows().ToList();

                progress.IndexCount = rowsd.Count;
                progress.IndexCurrent = 0;
                ShowProgress();

                using (var index = new SpeedSearch.FullText.FTIndexer(indexPath, true))
                {
                    Log.Info("Index: " + rowi.IndexName + " - " + indexPath);

                    progress.IndexCurrent++;

                    foreach (var rowd in rowsd)
                    {
                        AppendText("Reindexando: " + rowd.DirName, 1);
                        Application.DoEvents();
                        try
                        {
                            progress.IndexCurrent++;
                            progress.MesageIndex = "Localizando arquivos: " + rowd.Path;
                            ShowProgress();

                            //var files = Directory.GetFiles(rowd.Path, "*.*", SearchOption.AllDirectories).OrderBy(p => p).ToList();
                            var fse = new FileSystemInfoEnumerator(rowd.Path, "*.*", SearchOption.AllDirectories,
                                (ex) => ex.ToString());

                            // TODO: Melhora aqui, não retornando todas as linhas e usar um enumerator como no código abaixo. Só que vou ter que mudar a lógica desse método
                            //var enu = fs.GetEnumerator();

                            //while (enu.MoveNext())
                            //{
                            //    var fi = enu.Current;
                            //    writer.WriteLine(fi.FullName);
                            //}

                            var files = (from c in fse select c.FullName).OrderBy(p => p).ToList();

                            files.RemoveAll(p => p.Contains("FileListAbsolute.txt"));
                            files.RemoveAll(p => p.Contains("\\TempPE\\"));
                            files.RemoveAll(p => p.Contains("\\obj\\"));
                            Log.Info("\tDiretório: " + rowd.Path + " - " + files.Count);

                            var groupFiles = files.GroupByToDictionary(p => Path.GetExtension(p.ToUpper()));

                            List<string> exts = rowi.Extensions.Replace("*.", ".").ToUpper().Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries).ToList();
                            exts.Sort();
                            for (int i = 0; i < exts.Count; i++)
                                exts[i] = exts[i].Trim();

                            var searcher = new DocSearcher2(rowi.Path);

                            // TODO: aqui não é para retornar itens duplicados, mas está retornando. Tratar isso
                            //Dictionary<string, DocFile> documents = searcher.Search("dir", rowd.DirectoryId.ToString()).ToDictionary(p => p.FullName);
                            Dictionary<string, DocFile> documents = new Dictionary<string, DocFile>(StringComparer.InvariantCultureIgnoreCase);
                            var result = searcher.Search("dir", rowd.DirectoryId.ToString());
                            foreach (var item in result)
                                if (!documents.ContainsKey(item.FullName))
                                    documents.Add(item.FullName, item);

                            // arquivos à serem excluídos
                            var delDocs = new Dictionary<string, DocFile>(StringComparer.InvariantCultureIgnoreCase);
                            var xfiles = files.ToDictionary(p => p, StringComparer.InvariantCultureIgnoreCase);
                            foreach (var doc in documents)
                                if (!xfiles.ContainsKey(doc.Key))
                                    delDocs.Add(doc.Key, doc.Value);

                            //fts.Query("@dir: 27", "@dir").Length()
                            Dictionary<string, string> indexedFiles = new Dictionary<string, string>();

                            // muito lento
                            //foreach (var pair in groupFiles)
                            //{
                            //    string ext = pair.Key;
                            //    if (!exts.Contains(ext))
                            //    {
                            //        foreach (var f in groupFiles[ext])
                            //            _files.Remove(f);
                            //  x'  }
                            //}

                            bool allFiles = exts.Contains(".*");
                            files.Clear();
                            foreach (var pair in groupFiles)
                            {
                                string ext = pair.Key;
                                if (allFiles || exts.Contains(ext))
                                    files.AddRange(pair.Value);
                            }

                            files.Sort();
#if DEBUG2
                                files = files.Take(Math.Min(files.Count, 2)).ToList();
#endif

                            progress.MesageIndex = "Arquivos encontrados: " + files.Count;
                            progress.FileCount = files.Count;
                            progress.FileCurrent = 0;
                            progress.DeleteCount = delDocs.Count;
                            progress.DeleteCurrent = 0;
                            ShowProgress();

                            var parser = new SpeedSearch.FullText.Parsing.Parser(indexedFiles, notIndexedFiles);

                            int count = 0;
                            foreach (var file in files)
                            {
                                Application.DoEvents();

                                if (!progress.IsRunning)
                                    return;
                                
                                count++;
                                
                                try
                                {
                                    progress.FileCurrent++;
                                    progress.TotalFileCount++;

                                    progress.MesageCurrent = string.Format("Processando: {0} de {1} - {2}", progress.FileCurrent, progress.FileCount, file);
                                    ShowProgress();

                                    if (!indexedFiles.ContainsKey(file))
                                    {
                                        DocFile doc = null;

                                        if (documents.ContainsKey(file))
                                        {
                                            var docFile = documents[file];
                                            var imodified = docFile.DateModified;
                                            var fmodified = File.GetLastWriteTime(file);
                                            if (!FileHelper.HasChanged(fmodified, imodified))
                                            {
                                                progress.TotalFileUnchanged++;
                                                Log.Info("\t\t\tEquals: " + file);
                                                continue;
                                            }
                                            else
                                            {
                                                doc = documents[file];
                                            }
                                            Log.Info("\t\t\tUpdated: " + file);
                                        }
                                        indexedFiles.Add(file, null);

                                        FileInfo fi = new FileInfo(file);
                                        if (doc == null)
                                        {
                                            progress.TotalFileAdd++;
                                            Log.Info("\t\t\tAdded: " + file);
                                            index.AddDocument(fi, rowi.IndexId, rowd.DirectoryId);
                                        }
                                        else
                                        {
                                            progress.TotalFileChanged++;
                                            Log.Info("\t\t\tUpdated: " + file);
                                            index.UpdateDocument(doc.Document, fi, rowi.IndexId, rowd.DirectoryId);
                                        }
                                    }
                                    else
                                    {
                                        progress.TotalFileUnchanged++;
                                    }
                                }
                                catch (Exception ex)
                                {
                                    count.ToString();
                                    Log.Error(ex, "GenerateIndex 1");
                                    progress.Exception = ex;
                                    ShowProgress();
                                }
                                if (!progress.IsRunning)
                                    return;
                            }

                            ShowProgress();

                            // Exclusão
                            foreach (var pair in delDocs)
                            {
                                try
                                {
                                    var doc = pair.Value;
                                    progress.DeleteCurrent++;
                                    progress.TotalDeleteCount++;
                                    progress.MesageCurrent = string.Format("Removendo: {0} de {1} - {2}", progress.DeleteCurrent, progress.DeleteCount, pair.Key);
                                    ShowProgress();
                                    var query = searcher.QueryParser(pair.Key, "fname", EnumSearchMode.And);
                                    index.DeleteDocuments(query);
                                }
                                catch (Exception ex)
                                {
                                    Log.Error(ex, "GenerateIndex Del");
                                    progress.Exception = ex;
                                    ShowProgress();
                                }
                            }
                            ShowProgress();
                        }
                        catch (Exception ex)
                        {
                            Log.Error(ex, "GenerateIndex 1");
                            progress.Exception = ex;
                            ShowProgress();
                        }
                        if (!progress.IsRunning)
                            return;
                    }
                    
                    progress.MesageCurrent = "Otimizando índices";
                    ShowProgress();
                }
            }
            catch (Exception ex)
            {
                Log.Error(ex, "GenerateIndex 2");
                progress.Exception = ex;
                ShowProgress();
                Program.ShowError(ex);
            }
            Log.Info("Index: Fim - " + rowi.IndexName);
        }

        private void lblDirIndex_Click(object sender, EventArgs e)
        {
            Process.Start(Program.Db.Parameters.IndexDirectory);
        }

        private void button1_Click(object sender, EventArgs e)
        {
            Close();
        }

    }

}
